package dkbta.ontology.loading;

import static dkbta.ontology.elements.ElementType.CONTEXT;
import static dkbta.ontology.elements.ElementType.PATTERN;
import static dkbta.ontology.elements.ElementType.REPEATINGPATTERN;
import static dkbta.ontology.elements.ElementType.STAT;
import static dkbta.ontology.elements.ElementType.SUBJECTSTAT;
import static dkbta.ontology.loading.OntologyParser.parseValueConsdition;
import static dkbta.util.Utils.enumForName;

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import dkbta.ontology.Ontology;
import dkbta.ontology.elements.ElementType;
import dkbta.ontology.elements.TypedName;
import dkbta.ontology.elements.abstractions.TemporalSemanticProperty;
import dkbta.ontology.elements.abstractions.interpolation.PersistenceFunction;
import dkbta.ontology.elements.abstractions.state.AndMappingFunction;
import dkbta.ontology.elements.abstractions.state.StateDef;
import dkbta.ontology.elements.abstractions.state.StateMappingFunction;
import dkbta.ontology.elements.abstractions.state.StateMappingFunction.Entry;
import dkbta.ontology.values.ScaledSymbolicValues;
import dkbta.ontology.values.SymbolicValues;
import dkbta.ontology.values.ValueCondition;

public class StateParser extends AbstractionParser{

	public StateParser(Ontology ontology){
		super(ontology, EnumSet.of(CONTEXT, PATTERN, REPEATINGPATTERN, STAT, SUBJECTSTAT));
	}

	@Override
	public ElementType getElementType(){
		return ElementType.STATE;
	}

	@Override
	protected void parseAbstraction(Element state, String name, String componentType,
		Set<TypedName> abstractedFrom, Set<String> necessaryContexts,
		Set<TemporalSemanticProperty> tsps, ScaledSymbolicValues scaledValues,
		SymbolicValues values, PersistenceFunction persistFunc) throws OntologyException{
		StateMappingFunction mapFunc = parseMappingFunction(state, values, abstractedFrom);
		if (mapFunc == null){
			throw new OntologyException("State without a mapping function");
		}
		_ontology.addState(new StateDef(name, componentType, scaledValues,
				abstractedFrom, necessaryContexts, tsps, mapFunc, persistFunc));
	}

	private StateMappingFunction parseMappingFunction(Element state,
		SymbolicValues values, Set<TypedName> abstractedFrom) throws OntologyException{
		Element mappingFuncElement = OntologyParser.getXmlElement(state,
			"MappingFunction");
		if (mappingFuncElement == null){
			return null;
		}

		// Obtaining the number of entries and initializing the mapping function
		NodeList valueNodes = mappingFuncElement.getChildNodes();
		Collection<Entry> entries = new ArrayList<Entry>(valueNodes.getLength());
		StateMappingFunction mapFunc = initMappingFunc(mappingFuncElement, entries,
			abstractedFrom.size());

		for (int i = 0; i < valueNodes.getLength(); ++i){
			Node node = valueNodes.item(i);
			if (node.getNodeType() != Node.ELEMENT_NODE){
				continue;
			}
			Element valueElement = (Element)node;
			// Parsing the return/abstracted value for the entry
			String value = valueElement.getAttribute("name");
			String abstractedValue = values.parseAndValidate(value);
			if (abstractedValue == null){
				throw new OntologyException("Invalid/missing value '" + value
						+ "' in the mapping function");
			}

			// Parsing the entry and adding it to the mapping function
			Entry entry = parseEntry(abstractedFrom, mapFunc, valueElement,
				abstractedValue);
			entries.add(entry);
		}
		return mapFunc;
	}

	private Entry parseEntry(Set<TypedName> abstractedFrom, StateMappingFunction mapFunc,
		Element valueElement, String abstractedValue) throws OntologyException{
		NodeList constraintElements = valueElement.getChildNodes();
		int length = constraintElements.getLength();
		Map<TypedName, ValueCondition> constraints = new LinkedHashMap<TypedName, ValueCondition>(
				length);
		for (int j = 0; j < length; ++j){
			Node node = constraintElements.item(j);
			if (node.getNodeType() != Node.ELEMENT_NODE){
				continue;
			}
			// Parsing the value constraint
			Element vcElement = (Element)node;
			ValueCondition vc = parseValueConsdition(vcElement, vcElement.getTagName(),
				_invalidAbstractionElements, false);
			TypedName af = new TypedName(vc.getElementType(), vc.getElementName());

			// Checking that the entry refers to an element the state is abstracted from
			if (!abstractedFrom.contains(af)){
				throw new OntologyException(
						"Constraint on a non-abstracted-from element: " + af);
			}

			// Checking that there is only one value constraints per element
			if (constraints.containsKey(af)){
				throw new OntologyException("Duplicate constraint on the element " + af
						+ " in the value " + abstractedValue
						+ "' in the mapping function");
			}
			constraints.put(af, vc);
		}

		Entry entry = mapFunc.new Entry(abstractedValue, constraints);
		return entry;
	}

	private StateMappingFunction initMappingFunc(Element mappingFuncElement,
		Collection<Entry> entries, int numOfAbstractedFromElements)
			throws OntologyException{
		StateMappingFunction.Type type = enumForName(StateMappingFunction.Type.class,
			mappingFuncElement.getAttribute("type"));
		StateMappingFunction mapFunc = null;
		switch (type){
			case AND:
				mapFunc = new AndMappingFunction(entries, numOfAbstractedFromElements);
				break;
			default:
				throw new OntologyException(
						"No implementation for state mapping function type " + type);
		}
		return mapFunc;
	}

}
